{$X-}
{$R-}
PROGRAM Scroll;
{------------------------------------------------------------------------------------
     This is a simple program to demonstrate how to use scroll bars.
     You can scroll text or graphics or both.
     You can scroll horizontally or vertically.
     By Cary Clark, Macintosh Technical Support
     Copyright Apple Computer Inc., 1984
 -----------------------------------------------------------------------------------}

   USES
      {$U-}
      {$U Obj/MemTypes  } MemTypes,
      {$U Obj/QuickDraw } QuickDraw,
      {$U Obj/OSIntf    } OSIntf,
      {$U Obj/ToolIntf  } ToolIntf;

   CONST
      Horizontal =       1; {These are the choices in the menu 'Scroll Bar'}
      Vertical =         2;
      TextItem =         4;
      Graphics =         5;

      FileMenu =         1; {Resource numbers and position in the Menu bar}
      ScrollMenu =       2;

      NumOfRects =      30; {quantity of rectangles and strings to scroll around}
      NumOfStrings =    55;

   TYPE
      MyRectData =      ARRAY [1..NumOfRects] OF Rect; {Graphics structure: }
      MyRectPtr =       ^MyRectData;                   { an array of rectangles}
      MyRectHdl =       ^MyRectPtr;

   VAR
      hTE:              TEHandle;      {TextEdit handle}
      hScroll,                         {Horizontal scroll bar}
      vScroll:          ControlHandle; {Vertical scroll bar}
      MyWindow:         WindowPtr;     {Document window}
      hdlScrollMenu:    MenuHandle;    {Handle to the menu items}
      MyRect:           MyRectHdl;     {Handle to array of rectangles}
      originalPart:     INTEGER;       {1st part of the scroll bar hit}
      PageCorner,                      {Location of the upper left hand page corner}
      EventPoint:       Point;         {Where an event took place}
      growBoxRect,                     {area of the window reserved for the grow box}
      MyViewRect:       Rect;          {display rectangle containing scrollable data}
      doneFlag,                        {Set TRUE when the user selects 'Quit'}
      showText,                        {Set TRUE when text can be scrolled}
      showGraphics:     BOOLEAN;       {Set TRUE when graphics can be scrolled}

{-----------------------------------------------------------------------------------}

   PROCEDURE SetUpData;

{This procedure initializes two data structures; a TextEdit record and an array of
 rectangles.  Initially, only text and the vertical scrollbar will be displayed.}

      VAR
         MyString:      StringHandle;  {Temporary container for a string in the
                                       resource fork}
         counter:       INTEGER;       {Counters must be local to the procedure}
         destRect:      Rect;          {Rectangle containing the larger-than-the-
                                       screen page}

      BEGIN
{The TextEdit record is initialized by reading in a string from the application's
resource fork and then inserting it a number of times into the TextEdit record.}
         MyString := GetString(256);   {Get some text to play around with}

{Set the view as the portrect less the vertical scrollbar area.  The TextEdit
destRect will be set to the current window width plus an arbitrary value.}
         MyViewRect := MyWindow^.portrect;
         destRect := MyViewRect;
         destRect.right := destRect.right + 300;
         PageCorner.h := - destRect.left;
         PageCorner.v := - destRect.top;
         MyViewRect.right := MyViewRect.right - 15; {subtract width of scrollbar}
         hTE := TENew(destRect, MyViewRect);

         HLock(Pointer(MyString)); {Can't move if we are going to point to the text}
         FOR counter := 1 TO NumOfStrings DO {Create a TE record full of the string.}
            TEInsert(Pointer(Ord4(MyString^) + 1), {past the string's length byte}
             Length(MyString^^), hTE);
         HUnLock(Pointer(MyString)); {Free to move again}

         {Now, create a structure of rectangles.}
         MyRect := Pointer(NewHandle(Sizeof(MyRectData))); {240 bytes }
         FOR counter := 1 TO NumOfRects DO
            SetRect(MyRect^^[counter], counter * 23, counter * 20, counter *
             23 + 50, counter * 20 + 50);

         showText := TRUE;
         showGraphics := FALSE;
         ShowWindow(MyWindow); {Display the window and the text it contains}

         vScroll := GetNewControl(256, MyWindow); {vertical scrollbar}
         hScroll := GetNewControl(257, MyWindow); {horizontal scrollbar, not shown}
         SetRect(growBoxRect, vScroll^^.contrlRect.left+1,
          vScroll^^.contrlRect.bottom+1, myWindow^.portRect.right,
          myWindow^.portRect.bottom); {This area is set up for ValidRect, below.}

         CheckItem(hdlScrollMenu, Vertical, TRUE);
         CheckItem(hdlScrollMenu, TextItem, TRUE)
      END; {of SetUpData}

{------------------------------------------------------------------------------------}

   PROCEDURE GrafUpdate(whatpart: Rect);
{This is roughly the equivalent of what TEUpdate does with text.  The upper left hand
corner of the page is moved up and to the left to simulate a view further down and
to the right.  To more accurately resemble a Toolbox routine like TEUpdate, this
procedure should also preserve the current clip region and origin.}

      VAR
         count:         INTEGER;
         dummyRect:     Rect;

      BEGIN
         SetOrigin(PageCorner.h, PageCorner.v); {negative moves the origin left, up}
         OffsetRect(whatpart, PageCorner.h, PageCorner.v); {move the update rect.}
         ClipRect(whatpart); {only redraw the portion that the user requests}
         FOR count := 1 TO NumOfRects DO
           {Redraw the object if it intersects the update rectangle}
            IF SectRect(MyRect^^[count], whatpart, dummyRect)
               THEN FrameRect(MyRect^^[count]);
         SetOrigin(0, 0); {reset the origin back to the upper left hand corner}
         ClipRect(MyWindow^.portrect); {reset the clip region to the entire window}
      END; {of GrafUpdate}

{------------------------------------------------------------------------------------}

   PROCEDURE ScrollBits;
{This routine scrolls horizontally and vertically both graphics and text.  If you are
 only scrolling text, only the TEScroll is required.  If you are only scrolling
 graphics, then only the ScrollRect and GrafUpDate is needed.}

      VAR
         vChange, hChange, vScrollValue, hScrollValue: INTEGER;
         AnUpdateRgn: RgnHandle;

      BEGIN
         vScrollValue := GetCtlValue(vScroll); {These values will be used a lot so}
         hScrollValue := GetCtlValue(hScroll); {they are temporary variables.}

{find the vertical and horizontal change}
         vChange := PageCorner.v - vScrollValue; {the vertical difference}
         hChange := PageCorner.h - hScrollValue; {the horizontal difference}

{record the values for next time}
         PageCorner.v := vScrollValue;
         PageCorner.h := hScrollValue;

{for pure text, only a TEScroll is required}
         IF showText AND NOT showGraphics
            THEN TEScroll(hChange, vChange, hTE);

{For graphics, a ScrollRect will move the visible bits on the screen.  The
region returned by ScrollRect indicates what part of the window needs to
be updated.}
         IF showGraphics
            THEN BEGIN
               AnUpdateRgn := NewRgn;
               ScrollRect(MyViewRect, hChange, vChange, AnUpdateRgn);

{This draws the new text.  The clipping is necessary because normally
TextEdit redraws the entire character height and perhaps only a partial
character scroll was done.  Since TextEdit erases before it draws, the text,
if any, is drawn before the graphics.}
               IF showText
                  THEN
                     WITH hTE^^.destRect DO BEGIN
                        left := - hScrollValue;
                        top := - vScrollValue;
                        ClipRect(AnUpdateRgn^^.rgnbbox);
                        TEUpdate(AnUpdateRgn^^.rgnbbox, hTE);
                        ClipRect(MyWindow^.portrect)
                     END; {of showText}

               GrafUpdate(AnUpdateRgn^^.rgnbbox); {Fill in the newly exposed region.}
               DisposeRgn(AnUpdateRgn)
            END {of showGraphics}
      END; {of ScrollBits}

{------------------------------------------------------------------------------------}

   PROCEDURE TrackScroll(theControl: ControlHandle; partCode: INTEGER);
{This routine adjusts the value of the scrollbar.  A reasonable change would
be to adjust the minimum scroll amount to equal the text's lineheight.}

      VAR
         amount,
         StartValue:    INTEGER;
         up:            BOOLEAN;

      BEGIN
         up := (partCode = inUpButton) OR (partCode = inPageUp); {TRUE if
            scrolling up}
         StartValue := GetCtlValue(theControl); {the initial control value}

         IF
{The scrollbar value is decreased, and it is not already at the minimum.}
           ((up AND (StartValue > GetCtlMin(theControl))) OR
{The scrollbar value is increased, and it is not already at the maximum.}
           ((NOT up) AND (StartValue < GetCtlMax(theControl)))) AND
{to prevent tracking as the page up or down area disappears}
           (originalPart = partCode)
            THEN BEGIN
               IF up
                  THEN
                     amount := - 1
                  ELSE
                     amount := 1; {set the direction}
               IF (partCode = inPageUp) OR (partCode = inPageDown)
                  THEN BEGIN
                     {change the movement to a full page}
                     WITH MyViewRect DO
                        IF theControl = vScroll
                           THEN
                              amount := amount * (bottom - top)
                           ELSE
                              amount := amount * (right - left)
                  END; {of partCode}
               SetCtlValue(theControl, StartValue + amount);
               ScrollBits
            END
      END; {of TrackScroll}

{-----------------------------------------------------------------------------------}

   PROCEDURE MyControls; {respond to a mouse down event in one of the controls}

      VAR
         dummy:         INTEGER;
         theControl:    ControlHandle;

      BEGIN  {Get control and part.}
         originalPart := FindControl(EventPoint, MyWindow, theControl);
         IF originalPart = inThumb
            THEN BEGIN
{Thumb is tracked until it is released; then the bits are scrolled.}
               dummy := TrackControl(theControl, EventPoint, NIL);
               ScrollBits
            END {of whichpart}
{For the arrows and the page changes, scroll while the mouse is held down.}
            ELSE
               dummy := TrackControl(theControl, EventPoint, @TrackScroll)
      END; {of Mycontrols}

{-----------------------------------------------------------------------------------}

   PROCEDURE MainEventLoop;
{Respond to menu selections, the scrollbars, and update events.}

      VAR
         myEvent: EventRecord; {All of the information about the event}
         menuResult: LONGINT; {Information returned by MenuSelect}
         theMenu, {Which menu was selected}
         theItem: INTEGER; {Which item within the menu}
         checked: BOOLEAN; {Is the menu item checked}
         MarkChar: Char; {The checkmark character}
         tempWindow: WindowPtr;
         tempRect: Rect;

      BEGIN
         REPEAT
            checked := GetNextEvent(everyEvent, myEvent); {checked here is ignored}
            CASE myEvent.what OF
               mouseDown: BEGIN
{the user pressed or is holding the mouse button down}
                  CASE FindWindow(myEvent.where, tempWindow) OF

                     inMenuBar: BEGIN {the mouseDown was in the menu bar}
                        menuResult := MenuSelect(myEvent.where);
                        theMenu := HiWord(menuResult);
                        theItem := LoWord(menuResult);
                        CASE theMenu OF
                           FileMenu: doneFlag := TRUE; { Quit }
                           ScrollMenu: BEGIN
{The items in the menu are used to keep track of the user has chosen thus far. These
lines toggle the checkmark in the menu and leave the result in the variable checked.}
                            GetItemMark(hdlScrollMenu, theItem, MarkChar);
                            checked := MarkChar <> Chr(checkmark);
                            CheckItem(hdlScrollMenu, theItem, checked);

{Any selection will cause some part of the screen to be redrawn.  The selection that
the user makes causes some part of the screen to become invalid.}
                             IF (theItem = TextItem) OR (theItem = graphicsItem)
                                THEN BEGIN
                                   InvalRect(MyViewRect);
{The small area between the scrollbars reserved for the grow box should never be
redrawn.}
                                   ValidRect(growBoxRect)
                                END;
                             CASE theItem OF

                              Horizontal: BEGIN
                               InvalRect(hScroll^^.contrlrect);
                               IF checked
                                THEN BEGIN
                                 ShowControl(hScroll);
                                 MyViewRect.bottom := hScroll^^.contrlrect.top
                                END {checked}
                                ELSE BEGIN {not checked}
                                 HideControl(hScroll);
                                 MyViewRect.bottom := hScroll^^.contrlrect.bottom
                                END {not checked}
                              END; {horizontal}

                              Vertical: BEGIN
                               InvalRect(vScroll^^.contrlrect);
                               IF checked
                                THEN BEGIN
                                 ShowControl(vScroll);
                                 MyViewRect.right := vScroll^^.contrlrect.left
                                END {checked}
                                ELSE BEGIN {not checked}
                                 HideControl(vScroll);
                                 MyViewRect.right := vScroll^^.contrlrect.right
                                END {not checked}
                              END; {vertical}

                              TextItem: BEGIN
{Since we have dereferenced the destrect, no calls in the scope of this WITH should
cause a memory compaction.}
                               showText := checked;
                               IF checked
                                THEN
                                 WITH hTE^^.destRect DO BEGIN
                                  top := - GetCtlValue(vScroll);
                                  left := - GetCtlValue(hScroll);
                                 END {of checked}
                              END; {of textItem}

                               graphicsItem: showGraphics := checked;

                             END; {of CASE}
                             IF showText
                              THEN hTE^^.viewrect := MyViewRect
                           END {of inMenuBar}
                        END; {of FindWindow CASE}
                        HiliteMenu(0)
                     END; {of mouseDown}

                     inContent:
{The rectangles making up the controls are the part of the window outside the view.}
                        BEGIN
                        EventPoint := myEvent.where;
                        GlobalToLocal(EventPoint);
                        IF NOT PtInRect(EventPoint, MyViewRect)
                           THEN MyControls
                     END {in Content}
                  END {of CASE}
               END; {of mouseDown}

               updateEvent:
{In response to InvalRects, the appropriate text or graphics is erased and redrawn.
The BeginUpdate causes the VisRgn to be replaced by the intersection of the VisRgn
and the UpdateRgn.}
                  BEGIN
                  BeginUpdate(MyWindow);
                  EraseRect(MyViewRect); {start with a clean slate}
                  IF showText
                     THEN TEUpdate(MyWindow^.VisRgn^^.rgnbbox, hTE);
{Call GrafUpdate with the intersection, if any, of the VisRgn and the view}
                  IF showGraphics AND SectRect(MyWindow^.VisRgn^^.rgnbbox,
                   MyViewRect, tempRect)
                     THEN GrafUpdate(tempRect);
                  EndUpdate(MyWindow)
               END {of updateEvent}

            END {of event CASE}
         UNTIL doneFlag
      END;

{------------------------------------------------------------------------------------}

   BEGIN
      InitGraf(@ThePort); {initialize QuickDraw}
      InitWindows; {initialize Window Manager; clear desktop and menubar}
      InitFonts; {initialize Font Manager}
      FlushEvents(everyEvent, 0); {throw away any stray events}
      TEInit; {initialize TextEdit}
      InitMenus; {initialize Menu Manager}
      hdlScrollMenu := GetMenu(FileMenu); {(hdlScrollMenu is ignored)}
      InsertMenu(hdlScrollMenu, 0);
      hdlScrollMenu := GetMenu(ScrollMenu);
      InsertMenu(hdlScrollMenu, 0);
      DrawMenuBar;
      doneFlag := FALSE; {user 'Quit' flag}
      MyWindow := GetNewWindow(256, NIL, Pointer( - 1)); {get window to work within}
      SetPort(MyWindow); {point to window}
      TextFont(applFont); {select default application font}
      SetUpData; {initialize user data and controls}
      InitCursor; {change the watch into an arrow}
      MainEventLoop {handle events until we are through}
   END.
